%%%-------------------------------------------------------------------
%%% @author liuwentao
%%% @doc
%%%     角色触发器
%%% @end
%%% Created : 19. 7月 2021 14:25
%%%-------------------------------------------------------------------
-module(role_trigger).
-author("liuwentao").
-export([
    add/2               %% 增加触发器
    , del/2              %% 删除触发器
    , fire/2             %% 启动触发器
    , async_fire/2       %% 异步触发
    , async_fire/3       %% 异步触发
]).
-include("common.hrl").
-include("role.hrl").
-include("trigger.hrl").

%%----------------------------------------------------
%% 外部接口
%%----------------------------------------------------

%% @doc 添加一个或多个触发器
-spec add(Trigger, Role) -> Rtn when
    Trigger :: #trigger{} | [#trigger{}],
    Role :: #role{},
    Rtn :: {Tid, #role{}} | {[Tid], #role{}},
    Tid :: {atom(), pos_integer()}.
add(Triggers, Role) when is_list(Triggers) ->
    add(Triggers, Role, []);
add(T = #trigger{label = Label, id = Id}, Role = #role{trigger = {NextId, List}}) when is_integer(Id) == false ->
    NewList =
        case lists:keytake(Label, 1, List) of
            false -> [{Label, [T]} | List];
            {value, {_, T1}, RestList} ->
                case lists:keytake(Id, #trigger.id, T1) of
                    false ->
                        [{Label, [T | T1]} | RestList];
                    {value, _T2, RestList2} ->
                        [{Label, [T | RestList2]} | RestList]
                end
        end,
    {{Label, Id}, Role#role{trigger = {NextId + 1, NewList}}};
add(Trigger = #trigger{label = Label}, Role = #role{trigger = {NextId, List}}) ->
    T = Trigger#trigger{id = NextId},
    NewList =
        case lists:keytake(Label, 1, List) of
            false -> [{Label, [T]} | List];
            {value, {_, Tl}, RestList} -> [{Label, [T | Tl]} | RestList]
        end,
    {{Label, NextId}, Role#role{trigger = {NextId + 1, NewList}}}.
add([], Role, Ids) -> {lists:reverse(Ids), Role};
add([H | T], Role, Ids) ->
    {Id, NewRole} = add(H, Role),
    add(T, NewRole, [Id | Ids]).

%% @doc 删除一个或多个触发器
-spec del(Ids, #role{}) -> #role{} when
    Ids :: Id | [Id],
    Id :: {atom(), pos_integer()}.
del([], Role) -> Role;
del([H | T], Role) -> del(T, del(H, Role));
del({Label, Id}, Role = #role{trigger = {NextId, List}}) ->
    case lists:keytake(Label, 1, List) of
        false -> Role;
        %% 去除空列表
        {value, {_, []}, RestList} ->
            Role#role{trigger = {NextId, RestList}};
        {value, {_, Tl}, RestList} ->
            NewList =
                case lists:keydelete(Id, #trigger.id, Tl) of
                    %% 去除空列表
                    [] -> RestList;
                    NewTl -> [{Label, NewTl} | RestList]
                end,
            Role#role{trigger = {NextId, NewList}}
    end;
del(Label, Role) ->
    ?ERR("错误的触发器id:~w, 调用栈:~w", [Label, util:get_stacktrace()]),
    Role.

%% @doc 触发
-spec fire(Evt :: tuple() | list(), Role :: #role{}) -> NewRole :: #role{}.
fire([], Role) ->
    Role;
fire([Evt | T], Role) ->
    NRole = fire(Evt, Role),
    fire(T, NRole);
fire(Evt, Role = #role{name = _Name, trigger = {_, List}}) ->
    %% 事务处理
    case get(evt_transaction) of
        EvtTrans when is_list(EvtTrans) ->
            put(evt_transaction, [Evt | EvtTrans]),
            Role;
        _ ->
            Label = element(1, Evt),
            case lists:keyfind(Label, 1, List) of
                false -> Role;
                {_, Tlist} ->
                    call_trigger(lists:reverse(Tlist), Evt, Role)
            end
    end.

%% @doc  异步触发
async_fire(RolePid, _RoleId, Evts) when is_pid(RolePid) ->
    async_fire(RolePid, Evts);
async_fire(_RolePid, RoleId, Evts) ->
    async_fire(RoleId, Evts).

%% @doc 异步触发
-spec async_fire(pid() | tuple(), Evts :: tuple() | list()) -> ok.
async_fire(RolePid, Evts) when is_pid(RolePid) ->
    Fun = fun(Role, EvtL) ->
        NRole = fire(EvtL, Role),
        {ok, NRole}
          end,
    role:apply(async, RolePid, {Fun, [Evts]});
async_fire({RoleId, Platform, Zone}, Evts) ->
    case role_query:pid(by_id, RoleId, Platform, Zone) of
        {ok, RolePid} when is_pid(RolePid) ->
            async_fire(RolePid, Evts);
        _ ->
            skip
    end;
async_fire(_RoleInfo, _Evts) ->
    ?ERR("非法角色信息参数: ~w, ~w", [_RoleInfo, util:get_stacktrace()]),
    skip.

%%----------------------------------------------------
%% 私有函数
%%----------------------------------------------------

%% 处理触发器回调
call_trigger([], _Evt, Role) -> Role;
call_trigger([#trigger{label = Label, id = Id, m = M, f = F, a = A} | T], Evt, Role = #role{name = Name}) ->
    Rtn =
        if
            M =:= undefined ->
                catch erlang:apply(F, [Evt, Role] ++ A);
            true ->
                catch erlang:apply(M, F, [Evt, Role] ++ A)
        end,
    case Rtn of
        %% 保留当前触发器
        ok ->
            call_trigger(T, Evt, Role);
        %% 删除当前触发器
        remove ->
            call_trigger(T, Evt, del({Label, Id}, Role));
        %% 保留当前触发器，并返回新的角色数据
        {ok, #role{} = NewRole} ->
            call_trigger(T, Evt, NewRole);
        %% 删除当前触发器，并返回新的角色数据
        {remove, #role{} = NewRole} ->
            call_trigger(T, Evt, del({Label, Id}, NewRole));
        Err ->
            ?ERR("角色[~ts]调用[~w]触发器中的{~w, ~w}时发生异常: ~w", [Name, Label, M, F, Err]),
            call_trigger(T, Evt, Role)
    end.
